#!/usr/bin/perl
#------------------------------------------------------------------------------
# Safeproxy
#
#   Safeproxy is a simple http web proxy that gives parents the ability to 
#   limit the sites that the child accesses. Can be configured block sites, and 
#   to to only allow access to particular sites, and ask for a password for 
#   others.  Optionally it can cache results for quicker access, and reduce 
#   network bandwidth.
#
#------------------------------------------------------------------------------
#
# (c) Copyright Clinton Webb 2008
#
#		  This file is part of safeproxy.
#
#     Safeproxy is free software: you can redistribute it and/or modify
#     it under the terms of the GNU General Public License as published by
#     the Free Software Foundation, either version 3 of the License, or
#     (at your option) any later version.
# 
#     Safeproxy is distributed in the hope that it will be useful,
#     but WITHOUT ANY WARRANTY; without even the implied warranty of
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#     GNU General Public License for more details.
# 
#     You should have received a copy of the GNU General Public License
#     along with Safeproxy.  If not, see <http://www.gnu.org/licenses/>.
#
#------------------------------------------------------------------------------
#
#	Safe proxy is hosted by google.code.   
# The latest code and documentation can be obtained from:
#		http://code.google.com/p/safeproxy/
#
# The author can be contacted at:
#   webb.clint@gmail.com
# 
#------------------------------------------------------------------------------


#------------------------------------------------------------------------------
	use IO::Select;
	use IO::Socket;
	use Getopt::Long;
#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# Signal handling.  When the process gets a signal to exit, we want to handle 
# it nicely.  This is especially important since this script will most likely 
# be run as a daemon.  When the daemon tells it to shutdown, it needs to do it 
# nicely.
	my $signals = 0;
	sub catch_signal {
			$signals++;
	}
	$SIG{INT}  = \&catch_signal;
	$SIG{KILL} = \&catch_signal;
	$SIG{HUP}  = \&catch_signal;
	$SIG{TERM} = \&catch_signal;
#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# Use GetOpt to get the startup options.  Process the arguments passed to the 
# script.  We will have defaults for all the parameters.
	my $adminport = 8565;
	my $port = 8555;
	my $silent = 0;
	my $config = '/var/safeproxy/safeproxy.db';
	my $cachepath = '/var/safeproxy/cache';
	$result = GetOptions ("admin=i"    => \$adminport,
												"port=i"     => \$port,
												"silent"     => \$silent,
												"config=s"   => \$config,
												"cache=s"    => \$cachepath);
#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# 
#------------------------------------------------------------------------------
$cfgBuffSize = 2048;
#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# Open the config file.  We will need to make sure the config file is there, 
# and that it seems to be intact.  More information can be found at:
# http://code.google.com/p/safeproxy/wiki/ConfigFile
#
# TODO: need to connect to the database file, 
#
#------------------------------------------------------------------------------


#------------------------------------------------------------------------------
# Setup the socket listeners.  We have two because we have the general proxy 
# port, but also an administration console on a different port.

	# maximum number of connection requests before they get refused
	$maxconn = SOMAXCONN;

	# Setup the main socket listener.
	$server = IO::Socket::INET->new(Proto=>'tcp', LocalPort=>$port, Listen=>$maxconn, Reuse=>1)
    or die "Can't setup server on port $port\n";

	print "Server is: $server\n";


	# Setup the admin socket listener.  We allocate slightly less resources for 
	# this one, because it wont be used as much.
	$admin_server = IO::Socket::INET->new(Proto=>'tcp', LocalPort=>$adminport, Listen=>1, Reuse=>1)
    or die "Can't setup admin server on port $adminport\n";
#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# Now that we have both our socket listeners setup, we need to create our read 
# and write select list.  adding the two listener sockets to them.

	$rsel = new IO::Select( );
	$wsel = new IO::Select( );
	$esel = new IO::Select( );
	
	$rsel->add($server);
	$rsel->add($admin_server);

#------------------------------------------------------------------------------



#------------------------------------------------------------------------------
# Establish our global data hashes.   In a nice Object Oriented world, this 
# would be really poor programming, but here in flat perl land, we dont care so 
# much.

	# Socket type list.
	#   0 = unknown
	#   1 = client
	#   2 = admin
	#   3 = remote
	my (%stype);
	
	
	# we need to know the status of the socket.  For example, we need to know if 
	# it has already parsed the headers.  We also need to know if the connection 
	# to the remote host has been made, and we need to know if we are merely 
	# passing on everything.
	#   0 = unknown
	#   1 = new, hasn't parsed headers.
	#   2 = authenticating.
	#   3 = connecting to remote host.
	#   4 = Relay.  We are connected, so just relay everything.
	#   5 = Local cache.
	my (%smode);
	
	
	# we may also need to know what other socket is the connected pair.  For 
	# example, a client connects and requests a URL.  We then parse the URL and 
	# need to connect to the remote.   Both client and remote sockets need to 
	# relay data back and forth, so will need to know about each other;
	my (%other);


	# When the client connects and requests a URL that we are blocking, we will 
	# ask for a password.  Before asking for a password, we will generate a 
	# random set of numbers that will be included in the request.  The headers 
	# that were originally received will have been saved, and when those numbers 
	# are received and matched, the original headers will be used.  Also, keep 
	# in mind that the original socket will be closed after the request, and the 
	# password and options will be received on a new socket.
	my (%auth);


#------------------------------------------------------------------------------




#------------------------------------------------------------------------------
# Now we have our main loop.  In here we basically have two lists, read and 
# write.  As soon as resources are available for either, then the process 
# continues, with a list of handles that are available for reading or writing.   
# If the socket in the list is a listener, then we will need to accept the new 
# connection and add it to the read list.  When we get data on a socket, we 
# will need to process it.  If the processing involves needing to write to 
# another socket, then we will store the data and then add the other socket to 
# the list of handles waiting to write.  WHen it is available, the event 
# handler will let us know.

	while($signals == 0) {
		
		sleep 1;
		print "Starting select...\n";
#		my (@readlist, @writelist, @exceptlist) = IO::Select::select($rsel, undef, undef, 0);
		my ($readlist) = IO::Select->select($rsel, undef, undef, 30);
		print "Finished select...\n";
		
		foreach $fh (@$readlist) {
		
			print "Activity: $fh\n";
		
			if($fh == $server) {
				my $new = $server->accept;
				$rsel->add($new);
				
				print "###New connection: $new\n";
				
				$stype{$new} = 1
			}
			elsif ($fh == $admin_server) {
				my $new = $admin_server->accept;
				$rsel->add($new);
				$stype{$new} = 2;
				
				print "###New admin connection: $new\n";
				
			}
			else {
				# Process socket
				print "Socket handle: $fh\n";
				
				my $t_type = 0 + $stype{$fh};
				
				print "Socket type: $t_type\n\n";
				
		
				my $data;
				recv($fh, $data, $cfgBuffSize, 0);
				my $flen = length $data;
				print "Length: $flen\n";
				print "Data: $data\n\n";
	
				# normally we would want to check our mode, and if we are still gathering headers, we would want to 
	
				# Maybe we have finished with the socket
# 				$rsel->remove($fh);
# 				$fh->close;
				
				# if we are closing the socket, make sure we delete the socket from 
				# our hashes to keep memory usage somewhere manageable.
# 				delete $stype{$fh};
				
			}
		}
		
		foreach $fh (@$writelist) {
			# we have some socket handles that are ready to be written to.  The fact that we had this socket in our wsel list means that we have some data that we are wanting to send on this socket.
			
			# therefore, we need to make sure that there is data there.
			# and send it.   Since we are non-blocking (are we??), how does this work?   Will it attempt to send straight away, and if it can will do so?  Or will it return a false if it cant buffer all the data we are providing to it?
			
			
			
		}
		
		foreach $fh (@$exceptlist) {
			print "Got Exception on: $fh\n";
			
			# not really sure what an exception means in this context.   Will leave this code in and see what we get.
			
		}
	}
	
#------------------------------------------------------------------------------
	
	
	